﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Web.Security;
using Security;
using Stats;
using Storage;
using Storage.Exceptions;
using Storage.EntityModels;
using GeoRef;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Types;
using System.Transactions;

namespace Groupware
{
    /// <summary>
    /// This class logically represents a Trip.
    /// It is the entry point for trip operation like join request, reservation, etc.
    /// </summary>
    /// <remarks>Be sure you don't use Trip objects cross-request because they wrap DB entities binded to volatile DataContext</remarks>
    [DataContract] /* WebService REQUIRED Tag */
    public class Trip
    {
        #region Fields

        #region Field Internal
        internal const int NULL_ERROR = 0;
        internal const int NEGATIVE_VALUE_ERROR = 1;
        internal const int INCORRECT_DATE_ERROR = 2;
        internal const int INCORRECT_USER_ID = 3;
        internal const int INTERNAL_ERROR = 4;

        public struct Error
        {
            public int type;
            public string fieldName;
        }
        internal Storage.EntityModels.Trip dbTrip;
        #endregion
        
        private List<Error> errors;
        private Notification notify;
        private DateTime frequencyFrom;
        private DateTime frequencyTo;
        private string[] departureDayInfos;
      

        #endregion

        #region Constructors


        /// <summary>
        /// Construct a new Trip given a Storage Trip
        /// </summary>
        /// <param name="dbTrip"></param>
        protected internal Trip(Storage.EntityModels.Trip dbTrip)
        {
            this.dbTrip = dbTrip;
            this.errors = new List<Error>();
            this.notify = new Notification();
        }

        /// <summary>
        /// Construct a new Trip given the ownerID and a GeoTrip object
        /// </summary>
        /// <param name="ownerID"></param>
        /// <param name="geoTrip"></param>
        public Trip(int ownerID, GeoTrip geoTrip)
        {
            this.dbTrip = new Storage.EntityModels.Trip();
            // by Righetti: this line cannot be commented 'cause Georef uses later the Owner ref.
            this.dbTrip.Owner = Storage.StorageManager.Instance.GetUserById(ownerID);
            this.dbTrip.OwnerId = ownerID;
            geoTrip.FillDbTrip(this.dbTrip);
            errors = new List<Error>();
            this.Draft = true;
            this.notify = new Notification();
        }

        #endregion

        #region Properties

        internal Storage.EntityModels.Trip DbTrip
        {
            get { return dbTrip; }
            set { dbTrip = value; }
        }

        public String Arrival
        {
            get 
            { 
                //Modificato da WS per evitare eccezioni
                //Place p = new Place(dbTrip.Stages.ElementAt(0).Destination);
                var stages = dbTrip.Stages.OrderBy(s => s.Ordinal).ToList();
                if (stages.Count <= 0) return "";
                Place p = new Place(stages.ElementAt(stages.Count - 1).Destination);
                
                StringBuilder sb = new StringBuilder();
                if (!String.IsNullOrEmpty(p.Address))
                {
                    sb.Append(p.Address.Trim());
                    sb.Append(", ");
                }
                if (!String.IsNullOrEmpty(p.City))
                {
                    sb.Append(p.City.Trim());
                    sb.Append(", ");
                }
                if (!String.IsNullOrEmpty(p.Province))
                {
                    sb.Append(p.Province.Trim());
                    sb.Append(", ");
                }
                if (!String.IsNullOrEmpty(p.State))
                    sb.Append(p.State.Trim());
                return sb.ToString();
            }
        }

        public String Departure
        {
            get
            {
                //Modificato da WS per evitare eccezioni
                //Place p = new Place(dbTrip.Stages.ElementAt(0).Origin);
                var stages = dbTrip.Stages.OrderBy(s => s.Ordinal).ToList();
                if (stages.Count <= 0) return "";
                Place p = new Place(stages.ElementAt(0).Origin);

                StringBuilder sb = new StringBuilder();
                if (!String.IsNullOrEmpty(p.Address))
                {
                    sb.Append(p.Address.Trim());
                    sb.Append(", ");
                }
                if (!String.IsNullOrEmpty(p.City))
                {
                    sb.Append(p.City.Trim());
                    sb.Append(", ");
                }
                if (!String.IsNullOrEmpty(p.Province))
                {
                    sb.Append(p.Province.Trim());
                    sb.Append(", ");
                }
                if (!String.IsNullOrEmpty(p.State))
                    sb.Append(p.State.Trim());
                return sb.ToString();
            }
        }
        
        public Nullable<bool> AcceptSmoker 
        {
            get { return dbTrip.AcceptSmoker; }
            set 
            { 
                if (dbTrip.AcceptSmoker != null && dbTrip.AcceptSmoker != value) {notify.AddField("AcceptSmoker",value); }
                dbTrip.AcceptSmoker = value; 
            } 
        }

        public bool Accomplished
        {
            get { return dbTrip.Accomplished; }
            set 
            {
                //if (dbTrip.Accomplished != null && dbTrip.Accomplished != value) { notify.AddField("Accomplished", value);}
                notify.AddField("Accomplished", value);
                dbTrip.Accomplished = value;
            }
        }

        public Nullable<DateTime> DepartureDate
        {
            get
            {
                try { return dbTrip.DepartureDate; }

                catch (NullReferenceException)
                {
                    if (!dbTrip.Draft)
                    {
                        InternalError("DepartureDate", INTERNAL_ERROR);
                    }
                    return null;
                }

                catch (TripValidationException)
                {
                    InternalError("DepartureDate", INTERNAL_ERROR);
                    return null;
                }
            }

            set
            {
                if (!this.Draft && value == null) { InternalError("DepartureDate",NULL_ERROR); return; }
                if (((DateTime)value).CompareTo(DateTime.Now) <= 0) { InternalError("DepartureDate",INCORRECT_DATE_ERROR); return; }
                if (dbTrip.DepartureDate != null && ((DateTime)dbTrip.DepartureDate).CompareTo((DateTime)value)!= 0) { notify.AddField("DepartureDate", value); }
                dbTrip.DepartureDate = value;
            }
        }

        public Nullable<float> DeviationRadius 
        {
            get
            {
                try
                {
                    return dbTrip.DeviationRadius;
                }
                catch (TripValidationException)
                {
                    InternalError("DeviationRadius",INTERNAL_ERROR);
                    return null;
                }
            }
            set 
            {
                if (value == null) { InternalError("DeviationRadius", NULL_ERROR); dbTrip.DeviationRadius = 0; return; }
                if (value < 0) { InternalError("DeviationRadius",NEGATIVE_VALUE_ERROR); return; }
                if (dbTrip.DeviationRadius != null && dbTrip.DeviationRadius != value) { notify.AddField("DeviationRadius", value); }
                dbTrip.DeviationRadius = value;
            }
        }

        public bool Draft
        {
            get { return dbTrip.Draft; }
            set 
            { 
                //TODO: NOTIFY ????
                dbTrip.Draft = value;
            }
        }

        public Nullable<bool> RequireLicenced
        {
            get { return dbTrip.RequireLicenced; }
            set 
            {
                if (dbTrip.RequireLicenced != null && dbTrip.RequireLicenced != value) { notify.AddField("RequireLicenced", value); }
                dbTrip.RequireLicenced = value;
            }
        }

        public Nullable<decimal> EstimatedBudget
        {
            get
            {
                try
                {
                    return dbTrip.EstimatedBudget;
                }

                catch (TripValidationException)
                {
                    InternalError("EstimatedBudget",INTERNAL_ERROR);
                    return null;
                }
            }
            set 
            {
                Error e;
                if (value != null && value < 0) { InternalError("EstimatedBudget",NEGATIVE_VALUE_ERROR); return; }
                if (dbTrip.EstimatedBudget != null && dbTrip.EstimatedBudget != value){notify.AddField("EstimatedBudget", value);}  
                dbTrip.EstimatedBudget = value; 
            }
        }

        public Nullable<short> EstimatedTime
        {
            get
            {
                try
                {
                    return dbTrip.EstimatedTime;
                }
                catch (TripValidationException)
                {
                    InternalError("EstimatedTime",INTERNAL_ERROR);
                    return null;
                }
            }
            set 
            {
                Error e;
                if (value != null && value < 0) { InternalError("EstimatedTime", NEGATIVE_VALUE_ERROR); return; }
                if (dbTrip.EstimatedTime != null && dbTrip.EstimatedTime != value) { notify.AddField("EstimatedTime", value); }
                dbTrip.EstimatedTime = value; 
            }
        }

        public Nullable<byte> FreeSeats
        {
            get
            {
                try
                {
                    return dbTrip.FreeSeats;
                }

                catch (TripNotFoundException)
                {
                    InternalError("FreeSeats",INTERNAL_ERROR);
                    return null;
                }
            }
            set 
            {
                Error e;
                if (!this.Draft && value == null) { InternalError("FreeSeats", NULL_ERROR); return; }
                if (value < 0) { InternalError("FreeSeats", NEGATIVE_VALUE_ERROR); return; }
                if (dbTrip.FreeSeats != null && dbTrip.FreeSeats != value) { notify.AddField("FreeSeats", value); }
                dbTrip.FreeSeats = value; 
            }
        }
        public DateTime FrequencyFrom
        {
            set
            {
                if (value == null) { InternalError("FrequencyFrom", NULL_ERROR); return; }
                if (((DateTime)value).CompareTo(DateTime.Now) <= 0) { InternalError("FrequencyFrom", INCORRECT_DATE_ERROR); return; }
                frequencyFrom = value;
                this.IsFrequent = true;
            }
        }
        public DateTime FrequencyTo
        {
            set
            {
                if (value == null) { InternalError("FrequencyTo", NULL_ERROR); return; }
                if (((DateTime)value).CompareTo(DateTime.Now) <= 0) { InternalError("FrequencyTo", INCORRECT_DATE_ERROR); return; }
                frequencyFrom = value;
                this.IsFrequent = true;
            }
        }
        public string[] DepartureDayInfos
        {
            set { departureDayInfos = value; this.IsFrequent = true; }
        }
        public Nullable<bool> IsFrequent
        {
            get
            {
                try
                {
                    return dbTrip.IsFrequent;
                }

                catch (TripValidationException)
                {
                    InternalError("IsFrequent",INTERNAL_ERROR);
                    return null;
                }
            }
            set
            {
                Error e;
                if (!this.Draft && value == null) { InternalError("IsFrequent", NULL_ERROR); return; }
                //if (value < 0) { InternalError("RepetitionFrequency", NEGATIVE_VALUE_ERROR); return; }
                if (dbTrip.IsFrequent != null && dbTrip.IsFrequent != value) { notify.AddField("RepetitionFrequency", value); }
                dbTrip.IsFrequent = value; 
            }
        }

        public int Id
        {
            get { return dbTrip.Id; }
        }

        public Nullable<decimal> RealBudget
        {
            get
            {
                try
                {
                    return dbTrip.RealBudget;
                }
                catch (TripValidationException)
                {
                    InternalError("RealBudget",INTERNAL_ERROR);
                    return null;
                }
            }
            set 
            {
                Error e;
                if (value < 0) { InternalError("RealBudget", NEGATIVE_VALUE_ERROR); return; }
                if (dbTrip.RealBudget != null && dbTrip.RealBudget != value) { notify.AddField("RealBudget", value); }
                dbTrip.RealBudget = value; 
            }
        }

        public Nullable<short> RealTime
        {
            get
            {
                try
                {
                    return dbTrip.RealTime;
                }
                catch (TripValidationException)
                {
                    InternalError("RealTime",INTERNAL_ERROR);
                    return null;
                }
            }
            set 
            {
                Error e;
                if (value < 0) { InternalError("RealTime", NEGATIVE_VALUE_ERROR); return; }
                if (dbTrip.RealTime != null && dbTrip.RealTime != value) { notify.AddField("RealBudget", value); }
                dbTrip.RealTime = value; 
            }
        }

        public Nullable<DateTime> ReservationExpirationDate
        {
            get
            {
                try
                {
                    return dbTrip.ReservationExpirationDate;
                }
                catch (TripValidationException)
                {
                    InternalError("ReservationExpirationDate", INTERNAL_ERROR);
                    return null;
                }
            }
            set 
            {
                Error e;
                if (((DateTime)value).CompareTo(DepartureDate) > 0) { InternalError("ReservationExpirationDate", INCORRECT_DATE_ERROR); return; }
                if (dbTrip.ReservationExpirationDate != null && ((DateTime)dbTrip.ReservationExpirationDate).CompareTo((DateTime)value) != 0) { notify.AddField("ReservationExpirationDate", value); }
                dbTrip.ReservationExpirationDate = value; 
            }
        }

        /// <summary>
        /// Gets the frequency details of the trip.
        /// </summary>
        public Storage.EntityModels.FrequencyDetails FrequencyDetails
        {
            get { return dbTrip.FrequencyDetails; }
        }

        public string Notes
        {
            get { return dbTrip.Notes; }
            set { dbTrip.Notes = value; }
        }

        public Nullable<int> UserId
        {
            get { return dbTrip.OwnerId; }
        }

        public List<Error> Errors
        {
            get { return this.errors; }
        }

        #endregion

        #region Methods
        

        /// <summary>
        /// Returns user profiles of this trip's passengers
        /// </summary>
        /// <param name="user">the user performing the operation</param>
        /// <returns>a list of Profile objects</returns>
        public List<Profile> GetPassengersProfiles(TripLoaUser user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            List<Profile> list =
                (from Storage.EntityModels.Users_Stages us in StorageManager.Instance.Entities.Users_Stages
                 where us.Stage.Trip == dbTrip
                 select us.UserId)
                 .Distinct()
                 .ToList()
                 .ConvertAll(id => user.GetProfile(id));
            return list;
        }

        /// <summary>
        /// Get the GeoTrip object containing the geographical data for this trip
        /// </summary>
        /// <returns>a GeoTrip instance</returns>
        /// <remarks>avoid calling this method more than is needed</remarks>
        public GeoTrip GetGeoTrip()
        {
            return new GeoTrip(dbTrip);
        }

        /// <summary>
        /// Gets the pending reservation request submitted for this trip by a specified user
        /// </summary>
        /// <param name="user">the user</param>
        /// <returns>the reservation request</returns>
        public ReservationRequest GetPendingReservationRequest(TripLoaUser user)
        {
            if (user == null)
                throw new ArgumentNullException("user");
            return GetPendingReservationRequest(user.UserName);
        }

        private ReservationRequest GetPendingReservationRequest(String username)
        {
            var l = StorageManager.Instance.Entities.ReservationRequests
                .Where(r => r.TripId == Id && r.User.Username == username && r.Status == (byte)ReservationRequest.State.PENDING)
                .ToList();

            if (l.Count == 0)
                return null;
            return new ReservationRequest(l.First());
        }

        /// <summary>
        /// Retrieves all the refused reservation requests for this trip originally submitted by the specified user
        /// </summary>
        /// <param name="user">the user</param>
        /// <returns>the refused reservation request list</returns>
        public List<ReservationRequest> GetRefusedReservationRequests(TripLoaUser user)
        {
            if (user == null)
                throw new ArgumentNullException("user");
            return StorageManager.Instance.Entities.ReservationRequests
                .Where(r => r.TripId == Id && r.User.Id == user.UserID && r.Status == (byte)ReservationRequest.State.REFUSED)
                .ToList()
                .ConvertAll(r => new ReservationRequest(r));
        }

        /// <summary>
        /// Retrieves all pending reservation requests for this trip.
        /// </summary>
        /// <param name="owner">the trip owner</param>
        /// <returns>the reservation request list</returns>
        /// <exception cref="OperationNotAllowedException">if operation isn't performed by this trip owner</exception>
        public List<ReservationRequest> GetPendingReservationRequests(TripLoaUser owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (dbTrip.OwnerId != owner.UserID)
                throw new OperationNotAllowedException("Only the owner can access pending reservation requests for a trip");
            return dbTrip.ReservationRequests
                .Where(r => r.Status == (byte)ReservationRequest.State.PENDING)
                .ToList()
                .ConvertAll(r => new ReservationRequest(r));
        }

        /// <summary>
        /// Retrieves all Reservation confirmed for this trip.
        /// </summary>
        /// <param name="owner">the trip owner</param>
        /// <returns>the reservation list</returns>
        /// <exception cref="OperationNotAllowedException">if operation isn't performed by this trip owner</exception>
        public List<Reservation> GetReservations(TripLoaUser owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (dbTrip.OwnerId != owner.UserID)
                throw new OperationNotAllowedException("Only the owner can access reservations for a trip");
            List<Reservation> list = new List<Reservation>();
            var reservations =
            from Storage.EntityModels.Users_Stages us in StorageManager.Instance.Entities.Users_Stages
            where us.Stage.TripId == this.Id
            orderby us.UserId, us.Stage.Ordinal
            group us by us.UserId into g
            select new
            {
                User = g.First().User,
                UserStages = g.ToList()
            };

            Reservation r;
            foreach (var tmp in reservations)
            {
                r = new Reservation()
                {
                    SeatsNo = tmp.UserStages.First().RequestedSeats,
                    Start = new GeoLoc(tmp.UserStages.First().Stage.Origin),
                    End = new GeoLoc(tmp.UserStages.Last().Stage.Destination),
                    Username = tmp.User.Username,
                    UserId = tmp.User.Id,
                    StagesId = tmp.UserStages.ConvertAll(us => us.StageId),
                    TripId = this.Id
                };
                list.Add(r);
            }

            return list;
        }

        /// <summary>
        /// Checks whether a user has a pending reservation request for this trip.
        /// </summary>
        /// <param name="user">the user to check</param>
        /// <returns>true if a pending reservation request exists, false otherwise</returns>
        public bool IsRequestingReservation(TripLoaUser user)
        {
            if (user == null)
                throw new ArgumentNullException("user");
            return dbTrip.ReservationRequests
                .Where(r => r.UserId == user.UserID && r.Status == (byte)ReservationRequest.State.PENDING)
                .Count() != 0;
        }

        /// <summary>
        /// Checks whether a user has reservation confirmed for this trip.
        /// </summary>
        /// <param name="user">the user to check</param>
        /// <returns>true if a reservation exists, false otherwise</returns>
        public bool IsReserved(TripLoaUser user)
        {
            if (user == null)
                throw new ArgumentNullException("user");
            return StorageManager.Instance.Entities.Users_Stages
                .Where(us => user.UserID == us.UserId && us.Stage.TripId == Id)
                .Count() != 0;
        }
        
        /// <summary>
        /// Perform a reservation request on this Trip.
        /// </summary>
        /// <param name="user">the user requesting the reservation</param>
        /// <param name="seatsNo">the number of seats to reserve</param>
        /// <param name="start">the place to start from</param>
        /// <param name="end">the place to arrive to</param>
        /// <param name="startTolerance">tolerance for start place displacement</param>
        /// <param name="endTolerance">tolerance for end place displacement</param>
        /// <param name="notes">notes addressed to this trip owner</param>
        /// <returns>a ReservationRequest if the operation is performed, null otherwise</returns>
        /// <exception cref="OperationNotAllowedException">thrown if the user is not specified or correspond to the trip owner</exception>
        /// <exception cref="Trip.NotPublishedException"> thrown if this trip isn't published yet</exception>
        /// <exception cref="Trip.StillAccomplishedException">thrown if this trip is still accomplished</exception>
        /// <exception cref="Trip.ReservationTimeExpiredException">thrown if time for send requests has expired</exception>
        /// <exception cref="Trip.ReservationRequestStillSubmittedException">thrown if the specified user has still a pending reservation request for this trip</exception>
        /// <exception cref="Trip.ReservationStillConfirmedException">thrown if the specified user has still joined this trip</exception>
        /// <exception cref="Trip.ParametersException">
        /// thrown if one or more parameters provided are invalid
        /// (see Trip.ParametersException.Data property for further details; it contains [parameterName,Exception] pairs)
        /// </exception>
        public ReservationRequest RequestToJoin(
            TripLoaUser user,
            byte seatsNo,
            GeoLoc start,
            GeoLoc end,
            float startTolerance,
            float endTolerance,
            String notes)
        {
            if (user == null)
                throw new OperationNotAllowedException("Guest user cannot request to join trips");
            if (dbTrip.OwnerId == user.UserID)
                throw new OperationNotAllowedException("Trip owner cannot requests to join his own trip");
            if (Draft)
                throw new Trip.NotPublishedException();
            if (Accomplished)
                throw new Trip.StillAccomplishedException();
            if ((ReservationExpirationDate != null && ReservationExpirationDate.Value < DateTime.Now) ||
                (DepartureDate.Value < DateTime.Now))
                throw new Trip.ReservationTimeExpiredException();
            if (IsRequestingReservation(user))
                    throw new Trip.ReservationRequestStillSubmitted();
            if (IsReserved(user))
                    throw new Trip.ReservationStillConfirmedException();
            
            ReservationRequest r = new ReservationRequest()
            {
                UserId = user.UserID,
                Username = user.UserName,
                SeatsNo = seatsNo,
                Start = start,
                End = end,
                EndTolerance = endTolerance,
                StartTolerance = startTolerance,
                Notes = notes
            };
            IDictionary<String, Exception> errors = r.Validate(this);
            if (errors != null)
            {
                ParametersException e = new ParametersException("Wrong parameter(s)");
                foreach (KeyValuePair<String, Exception> err in errors)
                    e.Data.Add(err.Key, err.Value);
                throw e;
            }
            r.AddToDB(this);

            SendReservationReqNotification(user.UserID);

            return r;
        }

        /// <summary>
        /// Confirm a reservation on this trip.
        /// This operation is allowed to this trip's owner only.
        /// </summary>
        /// <param name="user">The user who's requesting the operation (the logged user)</param>
        /// <param name="trip">The geogrphical representation of this trip, modified in order to take/release the passenger</param>
        /// <param name="passengerID">The reservation request to be confirmed</param>
        /// <exception cref="OperationNotAllowedException">thrown if the specified user is not the trip owner</exception>
        /// <exception cref="Trip.NotEnoughSeatsException">thrown if there is at leas one stage with less free seats than than those the user asked to reserve</exception>
        /// <exception cref="ArgumentException">
        /// thrown if there isn't any reservation request on this travel pending for the user whose ID is given
        /// OR
        /// It has been impossible to determine the locations where the given user gets in or gets off this trip
        /// OR
        /// It has been impossible to determine the stages the given passenger joint
        /// OR
        /// It has been impossible to determine the travelmates' list for the given passengerID
        /// </exception>
        /// <exception cref="Trip.ParametersException">
        /// Thrown if the location choosen for the passenger to join or leave this trip is out of the tolerance specified by the given user in his reservation request
        /// (see Trip.ParametersException.Data property for further details; it contains [parameterName,Exception] pairs)
        /// </exception>
        /// <exception cref="Trip.NotEnoughSeatsException">
        /// thrown if the free seats are less than those the given user asked in his reservation request to be reserved
        /// </exception>
        public void ConfirmReservation(TripLoaUser user, GeoTrip trip, ReservationRequest request)
        {
            if (user.UserID != this.dbTrip.OwnerId) 
                throw new OperationNotAllowedException("The trip's owner only is allowed to confirm a reservation");
            if (request == null)
                throw new ArgumentNullException("request");
            
            int passengerID = request.UserId;

            using (TransactionScope transaction = new TransactionScope())
            {
                Storage.StorageManager dbMan = StorageManager.Instance;
                GeoLoc start = trip.GetInPlace(passengerID);
                GeoLoc end = trip.GetOffPlace(passengerID);
                if (start == null || end == null) throw new ArgumentException("Cannot find getting in or getting off location(s) for the specified passengerID");

                //Range check on start and end points choosen for the reservation
                IDictionary<String, Exception> errors = new Dictionary<string, Exception>();
                if (!request.CheckStartDistance(start))
                    errors.Add("start", new ArgumentException("Choosen location out of range"));
                if (!request.CheckEndDistance(end))
                    errors.Add("end", new ArgumentException("Choosen location out of range"));
                if (errors.Count != 0)
                {
                    ParametersException e = new ParametersException("Wrong parameter(s)");
                    foreach (KeyValuePair<String, Exception> err in errors)
                        e.Data.Add(err.Key, err.Value);
                    throw e;
                }

                //Check against available seats
                List<int> routesIDs = trip.GetModifiedRouteByPassenger(passengerID);
                int startRouteID;
                int endRouteID;
                if ((startRouteID = routesIDs.First()) == -1) throw new ArgumentException("Cannot find getting in or getting off route(s) for the specified passengerID");
                else endRouteID = routesIDs.ElementAt(1);
                List<Storage.EntityModels.Stage> stages = dbMan.GetAllStagesByTripId(this.Id);
                bool get = false;
                foreach (Storage.EntityModels.Stage stage in stages)
                {
                    if (stage.Id == startRouteID) get = true;
                    if (get)
                    {
                        //byte vehicleSeats = 0; //stage.Users_VehicleDetails.VehicleDetails.Seats;
                        //if ((this.FreeSeats == null ? vehicleSeats : Math.Min((byte)this.FreeSeats,vehicleSeats)) - stage.BusySeats < request.SeatsNo) throw new NotEnoughSeatsException();
                        if (this.FreeSeats - stage.BusySeats < request.SeatsNo) throw new NotEnoughSeatsException();
                    }
                    //else break;
                    if (stage.Id == endRouteID) break; // get = false;
                }

                trip.FillDbTrip(this.dbTrip);

                //Foreach stage involved in the confirmed reservation increments those stages' number
                //of BusySeats by the quantity of seats the passenger specified in his request,
                //and sets the number of reserved seats in the couple (passenger,stage) to that quantity too
                //PLEASE, MOVE THIS INTO GeoRef.GeoTrip.FillDbTrip !!
                foreach (Storage.EntityModels.Stage s in dbTrip.Stages)
                {
                    foreach (Storage.EntityModels.Users_Stages u_s in s.Users_Stages)
                    {
                        if (u_s.UserId == passengerID)
                        {
                            u_s.RequestedSeats = request.SeatsNo;
                            u_s.Stage.BusySeats += request.SeatsNo;
                            break;
                        }
                    }
                }

                request.DeleteFromDB();

                //Save modifications to DB
                dbMan.Entities.SubmitChanges();

                //Set pending feedbacks
                bool vehicleIsCar = this.dbTrip.Stages.First().Users_VehicleDetails.VehicleDetails.VehicleClass == Storage.EntityModels.VehicleTypes.Car;
                StatsModule.insertPendingFeedbacksOnPSNTripJoin(this.Id,passengerID,vehicleIsCar);

                //Change privacy level
                if (user.getRelation(passengerID) == PrivacyLevel.High) user.setRelation(passengerID, PrivacyLevel.Medium);

                //Send notification to travelmates       
                List<int> travelmatesIds = trip.GetUserFellowPassengers(passengerID);
                if (travelmatesIds == null) throw new ArgumentException("Cannot find travelmates for the specified passengerID");
                SendFellowshipNotification(travelmatesIds, passengerID);
                SendConfirmReservationNotification(passengerID);

                transaction.Complete();
            }
        }
        public int UpdateTrip(GeoTrip geoTrip)
        {
            if (geoTrip != null)
            {
                if (this.UserId < 0 || this.UserId == null)
                {
                    Trip.Error e = new Trip.Error();
                    e.type = Trip.INCORRECT_USER_ID;
                    e.fieldName = "UserID";
                    this.errors.Add(e);
                    return -1;
                }
                if (this.errors.Count > 0)
                    return -1;
                if (!this.Draft && this.CheckParametersXPublish() < 0)
                    return -1;
                Storage.EntityModels.TripLoaDataContext context = StorageManager.Instance.Entities;
                if (this.IsFrequent != null && this.IsFrequent == true)
                {
                    if (frequencyFrom != null && frequencyTo != null && departureDayInfos != null)
                    {
                        StorageManager.Instance.SetFrequencyDetails(dbTrip, frequencyFrom, frequencyTo, departureDayInfos);
                    }
                }
                geoTrip.FillDbTrip(dbTrip);
                context.SubmitChanges();
                SendModifyNotification();
                return this.Id;
            }
            else
            {
                Trip.Error e = new Trip.Error();
                e.type = Trip.NULL_ERROR;
                e.fieldName = "GeoTrip";
                this.errors.Add(e);
                return -1;
            }
        }
        /// <summary>
        /// Update a Trip
        /// </summary>
        /// <returns>-1 if there are errors</returns>
        /// <returns>-1 if this Trip is not Draft and data inserted is not correct</returns>
        public int UpdateTrip()
        {
            if (this.UserId < 0 || this.UserId == null)
            {
                Trip.Error e = new Trip.Error();
                e.type = Trip.INCORRECT_USER_ID;
                e.fieldName = "UserID";
                this.errors.Add(e);
                return -1;
            }
            if (this.errors.Count > 0)
                return -1;
            if (!this.Draft && this.CheckParametersXPublish() < 0)
                return -1;
            Storage.EntityModels.TripLoaDataContext context = StorageManager.Instance.Entities;
            if (this.IsFrequent != null && this.IsFrequent == true)
            {
                if (frequencyFrom != null && frequencyTo != null && departureDayInfos != null)
                {
                    StorageManager.Instance.SetFrequencyDetails(dbTrip, frequencyFrom, frequencyTo, departureDayInfos);
                }
            }
            context.SubmitChanges();
            SendModifyNotification();
            return this.Id;
        }

        /// <summary>
        /// Save a Trip
        /// </summary>
        /// <returns>-1 if there are errors</returns>
        /// <returns>-1 if this Trip is not Draft and data inserted is not correct</returns>
        public int SaveTrip()
        {
            if (this.UserId < 0 || this.UserId == null)
            {
                Trip.Error e = new Trip.Error();
                e.type = Trip.INCORRECT_USER_ID;
                e.fieldName = "UserID";
                this.errors.Add(e);
                return -1;
            }
            if (this.errors.Count > 0)
                return -1;
            if (!this.Draft && this.CheckParametersXPublish() < 0)
                return -1;
            Storage.EntityModels.TripLoaDataContext context = StorageManager.Instance.Entities;
            if (this.IsFrequent != null && this.IsFrequent == true)
            {
                if (frequencyFrom != null && frequencyTo != null && departureDayInfos != null)
                {
                    StorageManager.Instance.SetFrequencyDetails(dbTrip, frequencyFrom, frequencyTo, departureDayInfos);
                }
            }
            //context.Trips.InsertOnSubmit(this.dbTrip); NON VA MESSO!! CI PENSA GEOREF
            context.SubmitChanges();
            
            // Adds this Trip to the tracking system.

            //Modified by Umberto Lentini
            //If this is a draft trip it can't be tracked
            if (!this.Draft)
            {
                TripUpdater.Track(this);
            }
            
            return this.Id;
        }

        /// <summary>
        /// Checks whether the (air) distance of a geographical point from this trip route is lesser or equal
        /// to this trip DeviationRadius
        /// </summary>
        /// <param name="point">the point to check</param>
        /// <returns>true if the given point passed the check, false otherwise</returns>
        internal bool CheckDistance(GeoLoc point)
        {
            if (point == null)
                throw new ArgumentNullException("point");
            //BLOODY FUCKIN' LIBRARY!!! - INVERTS X & Y
            SqlGeography p = SqlGeography.Point(point.Lat, point.Lng, 4326);

            List<Storage.EntityModels.Stage> list =
                StorageManager.Instance.GetAllStagesByTripId(dbTrip.Id);
            
            //Creates a SqlGeography that is the entire trip path
            SqlGeography path = list.First().Path;
            list.RemoveAt(0);
            foreach (Storage.EntityModels.Stage s in list)
                path = s.Path.STUnion(path);
            
            return p.STBuffer((double)(dbTrip.DeviationRadius*1000.0)).STIntersects(path).Value;
        }

        private int CheckParametersXPublish()
        {
            Trip.Error e = new Trip.Error();
            if (this.Departure == null || this.Departure.Equals(""))
            {
                e.type = Trip.NULL_ERROR;
                e.fieldName = "Departure";
                this.errors.Add(e);
                return -1;
            }
            if (this.Arrival == null || this.Arrival.Equals(""))
            {

                e.type = Trip.NULL_ERROR;
                e.fieldName = "Arrival";
                this.errors.Add(e);
                return -1;
            }
            if (this.DepartureDate == null)
            {
                e.type = Trip.NULL_ERROR;
                e.fieldName = "DepartureDate";
                this.errors.Add(e);
                return -1;
            }
            try
            {
                if (this.FreeSeats < 0)
                {
                    e.type = Trip.NULL_ERROR;
                    e.fieldName = "FreeSeat";
                    this.errors.Add(e);
                    return -1;
                }
            }
            catch (ArgumentNullException)
            {
                //TEMPORARY PATCH
                this.FreeSeats = Byte.MaxValue;
            }
            if (this.UserId < 0)
            {
                e.type = Trip.INCORRECT_USER_ID;
                e.fieldName = "UserID";
                this.errors.Add(e); 
                return -1;
            }
            return this.Id;
        }

        /// <summary>
        /// Return a list of Vehicles associated to this trip
        /// </summary>
        /// <returns></returns>
        public List<Storage.EntityModels.VehicleDetails> GetVehicles()
        {
            return (from Storage.EntityModels.Stage s in dbTrip.Stages
                    select s.Users_VehicleDetails.VehicleDetails).ToList();
        }

        /// <summary>
        /// Return a list of Stages
        /// </summary>
        /// <returns></returns>
        public List<Stage> Stages()
        {
            return dbTrip.Stages.OrderBy(s => s.Ordinal).ToList().ConvertAll(s => new Stage(s));
        }

        /// <summary>
        /// Fills all the stages composing this Trip
        /// </summary>
        public void FillStages()
        {
            foreach (Storage.EntityModels.Stage s in dbTrip.Stages)
            {
                s.TripId = dbTrip.Id;
                s.DriverId = dbTrip.Owner.Id;
                s.ReservedSeats = 0;
                s.BusySeats = 0;
                //TODO: Controllare!!
                s.EstimatedBudget = 0;
            }
        }

        /// <summary>
        /// Updates the departure date of the Trip.
        /// In this case the Trip is a frequent one.
        /// </summary>
        internal void UpdateDepartureDate()
        {
            //NEED TEST!!!!!

            // Check assumption.
            System.Diagnostics.Debug.Assert(IsFrequent.Value,
                string.Format("Trip {0} has IsFrequent = false!", dbTrip.Id));


            //Select the next valid departure date.

            DayOfWeek today = DateTime.Now.DayOfWeek;

            DateTime FakeDepartureDate = (from f_df in dbTrip.FrequencyDetails.Frequency_DailyFrequencies
                                          where f_df.DailyFrequencyDetails.IsValid == true
                                          select f_df.DailyFrequencyDetails.FromHours)
                                          .Where(d => d.DayOfWeek > today)
                                          .OrderBy(d => d.Date)
                                          .FirstOrDefault();

            DateTime nextDepartureDate;

            if (FakeDepartureDate != default(DateTime))
            {
                nextDepartureDate = DateTime.Now.AddDays(FakeDepartureDate.DayOfWeek - DateTime.Now.DayOfWeek);
                nextDepartureDate = new DateTime(nextDepartureDate.Year, nextDepartureDate.Month, nextDepartureDate.Day,
                                                 FakeDepartureDate.Hour, FakeDepartureDate.Minute, FakeDepartureDate.Second);
            }

            else
            {
                FakeDepartureDate = (from f_df in dbTrip.FrequencyDetails.Frequency_DailyFrequencies
                                     where f_df.DailyFrequencyDetails.IsValid == true
                                     select f_df.DailyFrequencyDetails.FromHours).First();
                                     
                nextDepartureDate = DateTime.Now.AddDays(7 - (double)DateTime.Now.DayOfWeek + (double) FakeDepartureDate.DayOfWeek);
                nextDepartureDate = new DateTime(nextDepartureDate.Year, nextDepartureDate.Month, nextDepartureDate.Day,
                                                 FakeDepartureDate.Hour, FakeDepartureDate.Minute, FakeDepartureDate.Second);
            }
            
            //Updates Storage Trip       
            dbTrip.DepartureDate = nextDepartureDate;
            
            // Updates Groupware Trip
            DepartureDate = nextDepartureDate;
        }

        /// <summary>
        /// Sets the entry to handle the frequency of a Trip.
        /// </summary>
        /// <param name="trip">The trip.</param>
        /// <param name="frequencyFrom">Is DateTime: it marks the beginning of the frequency period.</param>
        /// <param name="frequencyTo">Is DateTime: it marks the end of the frequency period.</param>
        /// <param name="departureDayInfos">
        /// An Array of 7 position. It contains data for the new DepartureDate fields.
        /// departureDayInfo[0] contains frequency's infos for each Sunday beetween frequencyFrom and frequencyTo.
        /// departureDayInfo[1] contains frequency's infos for each Monday beetween frequencyFrom and frequencyTo.
        /// ...
        /// departureDayInfo[6] contains frequency's infos for each Saturday beetween frequencyFrom and frequencyTo.
        /// 
        /// If departureDayInfo[i].value == "DayNotScheduled", it means that Trips won't be repeated in day "i" of the week.
        /// departureDayInfo[i].value must have the format "hh:mm:ss" or "hh,mm,ss" or "hh.mm.ss", etc..
        /// </param>
        public void SetFrequencyDetails(DateTime frequencyFrom, DateTime frequencyTo, string[] departureDayInfos)
        {
            StorageManager.Instance.SetFrequencyDetails(dbTrip, frequencyFrom, frequencyTo, departureDayInfos);
            //this.FrequencyDetails.Frequency_DailyFrequencies[0].DailyFrequencyDetails.FromHours.Day;
        }

        //CHANGED FROM GUI//
        public string[] GetFrequencyDetails()
        {
            FrequencyDetails fd = this.FrequencyDetails;
            //bool isFirst = true;
            //int firstDay = -1;
            string[] dt_array = new string[7];

            int day_number = 0;
            foreach (Frequency_DailyFrequency fdf in fd.Frequency_DailyFrequencies)
            {
                if (day_number == 7) break;

                if (fdf.DailyFrequencyDetails.IsValid)
                    dt_array[day_number] = (string)fdf.DailyFrequencyDetails.FromHours.ToString("hh:mm:ss");
                else dt_array[day_number] = "DayNotScheduled";
               

                day_number++;

                /* if (isFirst)
                 {
                     firstDay = fdf.DailyFrequencyDetails.DayOfWeek;
                     isFirst = false;
                     dt_array[firstDay] = fdf.DailyFrequencyDetails.FromHours;
                 }
                 else
                 {
                     if (fdf.DailyFrequencyDetails.DayOfWeek != firstDay)
                     {
                         dt_array[fdf.DailyFrequencyDetails.DayOfWeek] = fdf.DailyFrequencyDetails.FromHours;
                     }
                     else
                     {
                         break;
                     }
                 }*/
            }
            return dt_array;
        }
        #endregion

        #region Private Methods

        private List<int> GetUserAssociated()
        {
            var result = new List<int>();

            foreach (Storage.EntityModels.Stage s in dbTrip.Stages)
                result.AddRange(StorageManager.Instance.GetUserListByStagePk(s.Id));

            return result.Distinct().ToList();
        }

        private void InternalError(String name,int type)
        {
            Error e = new Error();
            e.type = type;
            e.fieldName = name;
            errors.Add(e);
        }    
       
        #endregion
        
        #region Notification

        /// <summary>
        /// Retrives notification (RESERVATION_REQ) from user 'userId' addressed to trip's owner
        /// </summary>
        /// <returns></returns>
        public Notification GetReservationRequestNotification(int userId)
        {
            Notifications notification = null;

            try
            {
                notification = (from Storage.EntityModels.Notifications n in StorageManager.Instance.Entities.Notifications
                                where n.Message.TripId == dbTrip.Id && n.Message.Type == Notification.RESERVATION_REQ
                                      && n.UserId == userId
                                select n).ToList().First();
            }
            catch (InvalidOperationException) { return null; }

            Notification notify = new Notification()
            {
                SenderID = notification.UserId,
                IsRead = notification.IsRead,
                TripId = notification.Message.TripId,
                EventId = notification.Message.EventId,
                TemplateType = notification.Message.Type,
                Id = notification.Id
            };

            notify.XmlToNotification(notification.Message.Text);

            return notify;
        }

        /// <summary>
        /// Sends a notification to Trip's owner from user requesting
        /// a Reservation for this Trip
        /// </summary>
        /// <param name="passengerID">id of new user requesting reservation</param>
        public void SendReservationReqNotification(int passengerID)
        {
            Notification n = new Notification()
            {
                TemplateType = Notification.RESERVATION_REQ,
                TripId = dbTrip.Id,
                SenderID = passengerID
            };
            n.Store((int)dbTrip.OwnerId);
        }

        /// <summary>
        /// Create and send a notification to passenger travel mates
        /// It's used when a new user joined a trip after a ConfirmReservation()
        /// </summary>
        /// <param name="users">list of travel mates</param>
        /// <param name="passengerID">id of new passenger</param>
        public void SendFellowshipNotification(List<int> users, int passengerID)
        {
            if (users.Count == 0) return;
            Notification n = new Notification()
            {
                TemplateType = Notification.NOTIFY_FELLOWSHIP,
                TripId = dbTrip.Id,
                SenderID = dbTrip.OwnerId
            };
            n.AddField("newpassenger", passengerID);
            n.Store(users);
        }
        /// <summary>
        /// Create and send a new confirm notification to passenger whose request
        /// has been accepted by trip's owner
        /// It's used by ConfirmReservation()
        /// </summary>
        /// <param name="passengerID">id of new passenger</param>
        public void SendConfirmReservationNotification(int passengerID)
        {
            Notification n = new Notification()
            {
                TemplateType = Notification.CONFIRM_RESERVATION,
                TripId = dbTrip.Id,
                SenderID = dbTrip.OwnerId
            };
            n.Store(passengerID);
        }

        /// <summary>
        /// Send a new created trip notification (Notification.TRIP_CREATED) to friends of the trip's owner
        /// </summary>
        public void SendNewTripNotificationToFriends()
        {
            if (dbTrip.OwnerId.HasValue)
            {
                List<int> friends = StorageManager.Instance.GetFriendsByUserId(dbTrip.OwnerId.Value);
                if (friends.Count() == 0) return;
                Notification n = new Notification()
                {
                    TemplateType = Notification.TRIP_CREATED,
                    TripId = dbTrip.Id,
                    SenderID = dbTrip.OwnerId
                };
                n.Store(friends);
            }
        }

        /// <summary>
        /// Construct a new Notification for Modify_Trip Event
        /// </summary>
        public void SendModifyNotification()
        {
            notify.TemplateType = Notification.TRIP_MODIFIED;
            notify.SenderID = dbTrip.OwnerId;
            notify.TripId = dbTrip.Id;
            List<int> users = GetUserAssociated();

            var usersWithPendingRes = (from Storage.EntityModels.ReservationRequest r 
                                          in StorageManager.Instance.Entities.ReservationRequests
                                       where r.TripId == this.Id && r.Status == (byte) ReservationRequest.State.PENDING
                                       select r.UserId).Distinct().ToList();

            users.AddRange(usersWithPendingRes);

            if(users.Count != 0) notify.Store(users.Distinct().ToList());
        }
        
        #endregion

        #region Exceptions

        public class NotPublishedException : Exception { }

        public class ReservationTimeExpiredException : Exception { }

        public class StillAccomplishedException : Exception { }

        public class NotEnoughSeatsException : Exception { }

        public class ReservationRequestStillSubmitted : Exception { }

        public class ReservationStillConfirmedException : Exception { }

        #endregion
    }
}
